#include <ani.h>
#include <array>
#include <iostream>

ani_ref gCalc {};
ani_static_method gAdd {};
ani_method gSub {};
ani_field gResultField {};

static ani_int nativeCallSaticMethodFast(ani_env *env) {
    ani_int result {};
    for (int i = 0; i < 10; ++i) {
        // fast call without searching for method
        auto status = env->Class_CallStaticMethod_Int(static_cast<ani_class>(gCalc), gAdd, &result);
        if (status != ANI_OK) {
            std::cerr << "Calling static Method Fast Error" << status << std::endl;
            return status;
        }
    }
    return result;
}

static ani_int nativeCallSaticMethodSlow(ani_env *env) {
    ani_class cls {};
    const char *clsName = "ani_fast_call.Calc";
    auto status = env->FindClass(clsName, &cls);
    if (status != ANI_OK) {
        std::cerr << "Find Class '" << clsName << "' Error" << status << std::endl;
        return status;
    }
    ani_int result {};
    for (int i = 0; i < 10; ++i) {
        // slow call with searching for method
        status = env->Class_CallStaticMethodByName_Int(cls, "add", ":i", &result);
        if (status != ANI_OK) {
            std::cerr << "Calling static Method slow '" << clsName << "' Error" << status << std::endl;
            return status;
        }
    }

    return result;
}

static ani_int nativeCallMethodFast(ani_env *env, ani_object obj) {
    ani_int result {};
    for (int i = 0; i < 10; ++i) {
        // fast call without searching for method
        auto status = env->Object_CallMethod_Int(obj, gSub, &result);

        if (status != ANI_OK) {
            std::cerr << "Calling Method Fast Error " << status << std::endl;
            return status;
        }
    }
    return result;
}

static ani_int nativeCallMethodSlow(ani_env *env, ani_object obj) {

    ani_int result {};
    for (int i = 0; i < 10; ++i) {
        // slow call with searching for method
        auto status = env->Object_CallMethodByName_Int(obj, "sub", ":i", &result);

        if (status != ANI_OK) {
            std::cerr << "Calling Method slow Error" << status << std::endl;
            return status;
        }
    }
    return result;
}

static ani_int nativeAccessFieldFast(ani_env *env, ani_object obj) {
    ani_int result {};
    for (int i = 0; i < 10; ++i) {
        // fast call without searching for field
        auto status = env->Object_GetField_Int(obj, gResultField, &result);

        if (status != ANI_OK) {
            std::cerr << "AccessFieldFast Error" << status << std::endl;
            return status;
        }
    }
    return result;
}


static ani_int nativeAccessFieldSlow(ani_env *env, ani_object obj) {

    ani_int result {};
    for (int i = 0; i < 10; ++i) {
        // fast call with searching for field
        auto status = env->Object_GetFieldByName_Int(obj, "result", &result);

        if (status != ANI_OK) {
            std::cerr << "AccessFieldSlow Error" << status << std::endl;
            return status;
        }
    }
    return result;
}

ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result)
{
    ani_env *env;
    if (ANI_OK != vm->GetEnv(ANI_VERSION_1, &env)) {
        std::cerr << "Unsupported ANI_VERSION_1" << std::endl;
        return ANI_ERROR;
    }

    static const char *moduleName = "ani_fast_call";
    ani_module md;
    if (ANI_OK != env->FindModule(moduleName, &md)) {
        std::cerr << "Not found '" << moduleName << "'" << std::endl;
        return ANI_ERROR;
    }

    std::array functions = {
        ani_native_function {"callSaticMethodFast", nullptr, reinterpret_cast<void *>(nativeCallSaticMethodFast)},
        ani_native_function {"callSaticMethodSlow", nullptr, reinterpret_cast<void *>(nativeCallSaticMethodSlow)},
        ani_native_function {"callMethodFast", nullptr, reinterpret_cast<void *>(nativeCallMethodFast)},
        ani_native_function {"callMethodSlow", nullptr, reinterpret_cast<void *>(nativeCallMethodSlow)},
        ani_native_function {"accessFieldFast", nullptr, reinterpret_cast<void *>(nativeAccessFieldFast)},
        ani_native_function {"accessFieldSlow", nullptr, reinterpret_cast<void *>(nativeAccessFieldSlow)},
    };

    if (ANI_OK != env->Module_BindNativeFunctions(md, functions.data(), functions.size())) {
        std::cerr << "Cannot bind native methods to '" << moduleName << "'" << std::endl;
        return ANI_ERROR;
    };

    // preparing for fast call
    ani_class clsCalc {};
    const char *clsName = "Calc";
    if (ANI_OK != env->Module_FindClass(md, clsName, &clsCalc)) {
        std::cerr << "Not found '" << clsName << "'" << std::endl;
        return ANI_ERROR;
    }
    if (ANI_OK != env->GlobalReference_Create(static_cast<ani_ref>(clsCalc), &gCalc)) {
        std::cerr << "Create Global Calc failed" << std::endl;
        return ANI_ERROR;
    }
    if (ANI_OK != env->Class_FindStaticMethod(clsCalc, "add", ":i", &gAdd)) {
        std::cerr << "get static method add" << std::endl;
        return ANI_ERROR;
    }
    if (ANI_OK != env->Class_FindMethod(clsCalc, "sub", ":i", &gSub)) {
        std::cerr << "Get method sub" << std::endl;
        return ANI_ERROR;
    }
    if (ANI_OK != env->Class_FindField(clsCalc, "result", &gResultField)) {
        std::cerr << "Class Find Field failed" << std::endl;
        return ANI_ERROR;
    }
    *result = ANI_VERSION_1;
    return ANI_OK;
}